#!/bin/bash -X

# From "http://fedoraproject.org/wiki/FCNewInit/Initscripts":
#
# Status Exit Codes
#
#  0 program is running or service is OK
#  1 program is dead and /var/run pid file exists
#  2 program is dead and /var/lock lock file exists
#  3 program is not running
#  4 program or service status is unknown
#  5-99 reserved for future LSB use
#  100-149 reserved for distribution use
#  150-199 reserved for application use
#  200-254 reserved
#
# Non-Status Exit Codes
#
#  0 action was successful
#  1 generic or unspecified error (current practice)
#  2 invalid or excess argument(s)
#  3 unimplemented feature (for example, "reload")
#  4 user had insufficient privilege
#  5 program is not installed
#  6 program is not configured
#  7 program is not running
#  8-99    reserved for future LSB use
#  100-149 reserved for distribution use
#  150-199 reserved for application use
#  200-254 reserved
#

# load default, system-wide, and user-specific PKI configuration and
# set NSS_DEFAULT_DB_TYPE.
. /usr/share/pki/scripts/config

default_error=0

if [ -f /etc/debian_version ]; then
    debian=true
    SYSCONFIG_PKI=/etc/dogtag
else
    debian=false
    SYSCONFIG_PKI=/etc/sysconfig/pki
fi

case $command in
    start)
        # 1 generic or unspecified error (current practice)
        default_error=1
        ;;
    *)
        # 2 invalid argument(s)
        default_error=2
        ;;
esac

# Enable nullglob, if set then shell pattern globs which do not match any
# file returns the empty string rather than the unmodified glob pattern.
shopt -s nullglob

OS=`uname -s`
ARCHITECTURE=`arch`

# Check to insure that this script's original invocation directory
# has not been deleted!

# shellcheck disable=SC2034
CWD=`/bin/pwd > /dev/null 2>&1`
if [ $? -ne 0 ] ; then
    echo "Cannot invoke '$PROG_NAME' from non-existent directory!"
    exit ${default_error}
fi


PKI_REGISTRY_ENTRY="${PKI_REGISTRY}/${pki_instance_id}/${pki_instance_id}"
PKI_SUBSYSTEMS=""

usage()
{
    echo "Usage: /usr/bin/pkidaemon start <instance-name>"
    echo
}

list_instances()
{
    echo
    for INSTANCE in $SYSCONFIG_PKI/tomcat/*; do
        if [ -d "${INSTANCE}" ] ; then
            instance_name=`basename ${INSTANCE}`
            echo "    $instance_name"
        fi
    done
    echo
}

# Check arguments
if [ $# -lt 2 ] ; then
    #     [insufficient arguments]
    echo "$PROG_NAME:  Insufficient arguments!"
    echo
    usage
    echo "where valid instance names include:"
    list_instances
    exit 3
elif [ ${default_error} -eq 2 ] ; then
    # 2 invalid argument
    echo "$PROG_NAME:  Invalid arguments!"
    echo
    usage
    echo "where valid instance names include:"
    list_instances
    exit 2
elif [ $# -gt 2 ] ; then
    echo "$PROG_NAME:  Excess arguments!"
    echo
    usage
    echo "where valid instance names include:"
    list_instances
    # 2 excess arguments
    exit 2
fi

if [ ! -f ${PKI_REGISTRY_ENTRY} ]; then
    echo -n "${pki_instance_id} is an invalid instance"
    echo
    # 5 program is not installed
    exit 5
fi

backup_instance_configuration_files()
{
    declare -a pki_subsystems=('ca'
                               'kra'
                               'ocsp'
                               'tks'
                               'tps')

    # Utilize an identical timestamp on archives for each PKI subsystem
    # residing within the same instance to mark a common archival time
    timestamp=`date +%Y%m%d%H%M%S`

    # Automatically enable timestamped archives
    #
    #     NOTE:  To disable this feature for a particular PKI subsystem
    #            within an instance, edit that PKI subsystem's 'CS.cfg' file
    #            within the instance:
    #
    #            If the 'archive.configuration_file' parameter exists,
    #            change it to 'archive.configuration_file=false'.
    #
    #            However, if the 'archive.configuration_file' parameter does
    #            not exist, simply add 'archive.configuration_file=false'
    #            to the 'CS.cfg'.
    #
    #            In either case, it is unnecessary to restart the instance,
    #            as each instance's 'CS.cfg' file is always processed every
    #            time an instance is restarted.
    #
    backup_errors=0
    for pki in "${pki_subsystems[@]}"
    do
        config_dir=${PKI_INSTANCE_PATH}/conf/${pki}

        # Check to see if this PKI subsystem exists within this instance
        if [ ! -d ${config_dir} ] ; then
            continue
        fi

        # Compute uppercase representation of this PKI subsystem
        PKI=${pki^^}

        # Backup parameters
        pki_instance_configuration_file=${config_dir}/CS.cfg
        backup_file=${config_dir}/CS.cfg.bak
        saved_backup_file=${config_dir}/CS.cfg.bak.saved

        # Check for an empty 'CS.cfg'
        #
        #     NOTE:  'CS.cfg' is always a regular file
        #
        if [ ! -s ${pki_instance_configuration_file} ] ; then
            # Issue a warning that the 'CS.cfg' is empty
            echo "WARNING:  The '${pki_instance_configuration_file}' is empty!"
            echo "          ${PKI} backups will be discontinued until this"
            echo "          issue has been resolved!"
            ((backup_errors++))
            continue
        fi

        # Make certain that a previous attempt to backup 'CS.cfg' has not failed
        # (i. e. - 'CS.cfg.bak.saved' exists)
        #
        #     NOTE:  'CS.cfg.bak.saved' is always a regular file
        #
        if [ -f ${saved_backup_file} ] ; then
            # 'CS.cfg.bak.saved' is a regular file or a symlink
            echo "WARNING:  Since the file '${saved_backup_file}' exists, a"
            echo "          previous backup attempt has failed!  ${PKI} backups"
            echo "          will be discontinued until this issue has been resolved!"
            ((backup_errors++))
            continue
        fi

        # If present, compare 'CS.cfg' to 'CS.cfg.bak' to see if it is necessary
        # to backup 'CS.cfg'.  'CS.cfg.bak' may be a regular file, a
        # symlink, or a dangling symlink
        #
        #     NOTE:  'CS.cfg.bak' may be a regular file, a symlink, or a
        #            dangling symlink
        #
        if [ -f ${backup_file} ] ; then
            # 'CS.cfg.bak' is a regular file or a symlink
            cmp --silent ${pki_instance_configuration_file} ${backup_file}
            rv=$?
            if [ $rv -eq 0 ] ; then
                # 'CS.cfg' is identical to 'CS.cfg.bak';
                # no need to archive or backup 'CS.cfg'
                continue
            fi

            # Since it is known that the previous 'CS.cfg.bak' file exists, and
            # and it is either a symlink or a regular file, save the previous
            # 'CS.cfg.bak' to 'CS.cfg.bak.saved'
            #
            # NOTE:  If switching between simply creating backups to generating
            #        timestamped archives, the previous 'CS.cfg.bak' that
            #        existed as a regular file will NOT be archived!
            #
            if [ -h ${backup_file} ] ; then
                # 'CS.cfg.bak' is a symlink
                # (i. e. - copy the timestamped archive to a regular file)
                cp ${backup_file} ${saved_backup_file}

                # remove the 'CS.cfg.bak' symlink
                rm ${backup_file}
            else
                # 'CS.cfg.bak' is a regular file
                # (i. e. - simply rename the regular file)
                mv ${backup_file} ${saved_backup_file}
            fi
        elif [ -h ${backup_file} ] ; then
            # 'CS.cfg.bak' is a dangling symlink
            echo "WARNING:  The file '${backup_file}' is a dangling symlink"
            echo "          which suggests that the previous backup file has"
            echo "          been removed!  ${PKI} backups will be discontinued"
            echo "          until this issue has been resolved!"
            ((backup_errors++))
            continue
        fi

        # Check 'CS.cfg' for 'archive.configuration_file' parameter
        # to see if timestamped archives should be disabled
        archive_configuration_file="true"
        line=`grep -e '^[ \t]*archive.configuration_file[ \t]*=' ${pki_instance_configuration_file}`
        if [ "${line}" != "" ] ; then
            archive_configuration_file=`echo "${line}" | sed -e 's/^[^=]*[ \t]*=[ \t]*\(.*\)/\1/' -e 's/[ \t]*$//'`
        fi

        # Backup 'CS.cfg'
        if [ "${archive_configuration_file}" != "true" ] ; then
            # Always backup 'CS.cfg' to 'CS.cfg.bak'
            cp -b ${pki_instance_configuration_file} ${backup_file}
        else
            # Archive parameters
            archive_dir=${config_dir}/archives
            archived_file=${archive_dir}/CS.cfg.bak.${timestamp}

            # If not present, create an archives directory for this 'CS.cfg'
            if [ ! -d ${archive_dir} ] ; then
                mkdir -p ${archive_dir}
            fi

            # Archive 'CS.cfg' to 'CS.cfg.bak.${timestamp}'
            cp -a ${pki_instance_configuration_file} ${archived_file}
            if [ ! -s ${archived_file} ] ; then
                # Issue a warning that the archived backup failed
                echo "WARNING:  Failed to archive '${pki_instance_configuration_file}' to '${archived_file}'!"
                ((backup_errors++))
                continue
            fi

            # Always create 'CS.cfg.bak' by linking to this archived file
            ln -s ${archived_file} ${backup_file}
        fi

        # Check that a non-empty 'CS.cfg.bak' symlink or regular file exists
        if [ ! -s ${backup_file} ] ; then
            # Issue a warning that the backup failed
            echo "WARNING:  Failed to backup '${pki_instance_configuration_file}' to '${backup_file}'!"
            ((backup_errors++))
            continue
        fi

        # Since 'CS.cfg' was backed up successfully, remove 'CS.cfg.bak.saved'
        if [ -f ${saved_backup_file} ] ; then
            rm ${saved_backup_file}
        fi
    done

    if [ ${backup_errors} -ne 0 ]; then
        return 1
    fi

    return 0
}

check_deprecated_algorithms() {

    # Check deprecated algorithms in instance config folder which contains
    # subsystem config files such as CA cert profiles as well.
    # Exclude NSS database files and links to Tomcat standard config files.
    # The result will appear in systemd journal when the server is started.

    FILES=$(find /etc/pki/${pki_instance_id} -type f -not -path "/etc/pki/${pki_instance_id}/alias/*")

    grep -n -E "SHA\s*$|SHA\s*,|SHAwith|SHA1|SHA-1|SHA_1" $FILES | awk -F: '{ print "WARNING: Deprecated algorithm in " $1 ":" $2 ": " $3 }'
}

start_instance()
{
    rv=0

    check_deprecated_algorithms

    # Always create a backup of each PKI subsystem's 'CS.cfg' file
    # within an instance.
    #
    # For every backup failure detected within a PKI subsystem within
    # an instance, a warning message will be issued, and an error code
    # of 1 will be returned.
    #
    # Note that until they have been resolved, every previous backup
    # failures of any PKI subsystem within an instance will also issue
    # a warning message and return an error code of 1.  Backups of that
    # particular instance's PKI subsystem will be suspended until this
    # error has been addressed.
    #
    # By default, unless they have been explicitly disabled,
    # a timestamped archive of each PKI subsystem's 'CS.cfg' file
    # within an instance will also be created. Note that a single
    # timestamp will be utlized across each PKI subsystem within
    # an instance for each invocation of this function.
    #
    # When enabled, any timestamped archive failures also issue a
    # warning message and return an error code of 1.
    #
    backup_instance_configuration_files
    return $?
}

start()
{
    error_rv=0
    rv=0
    config_errors=0
    errors=0

    # Source values associated with this particular PKI instance
    [ -f ${PKI_REGISTRY_ENTRY} ] &&
    . ${PKI_REGISTRY_ENTRY}

    start_instance
    rv=$?

    if [ $rv = 6 ] ; then
        # Since at least ONE configuration error exists, then there
        # is at least ONE unconfigured instance from the PKI point
        # of view.
        #
        # However, it must still be considered that the
        # instance is "running" from the point of view of other
        # OS programs such as 'chkconfig'.
        #
        # Therefore, ignore non-zero return codes resulting
        # from configuration errors.
        #

        config_errors=`expr $config_errors + 1`
        rv=0
    elif [ $rv != 0 ] ; then
        errors=`expr $errors + 1`
        error_rv=$rv
    fi

    return $rv
}
